﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
using System.IO;
using System.Web.UI;
using TA.DotNetUtils;
using TA.JSON;
using TA.Turbo.Design;
using TA.Win.Utils.Design;

namespace TA.Turbo.Controls
{
	/// <summary>
	/// Outputs a collection of data objects in a templated fashion.
	/// </summary>
	[PersistChildren(false)]
	[ParseChildren(true)]
	[Designer(typeof(RepeaterDesigner))]
	public partial class Repeater : BaseJsonValueControl<RepeaterValue>, INamingContainer
	{
		#region INIT
		/// <summary>
		/// Ctor.
		/// </summary>
		public Repeater()
		{
			this.EnableServerRender = true;
			this.ViewState["Pager"] = new PagerSettings();

			this._value._owner = this;

			this._header = new SingleInstanceTemplate("Header");
			this._footer = new SingleInstanceTemplate("Footer");

			this._viewItem = new MultipleInstanceTemplate("ViewItem");
			this._editItem = new MultipleInstanceTemplate("EditItem");
			this._renderedItemsContainer = this.CreateRenderedItemsContainer();

			this.Controls.Add(new RepeaterPagerContainer(this, "top", PagerPosition.Top) { ID = "_PT" });
			this.Controls.Add(this._header.Container);
			this.Controls.Add(this._renderedItemsContainer);
			this.Controls.Add(this._footer.Container);
			this.Controls.Add(new RepeaterPagerContainer(this, "bottom", PagerPosition.Bottom) { ID = "_PB" });

			this.Controls.Add(this._viewItem.Container);
			this.Controls.Add(this._editItem.Container);
		}
		#endregion
		#region EVENTS
		/// <summary>
		/// Raised when an item needs to be data bound, on the server side during render or on the client side during operation.
		/// </summary>
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		[Editor(typeof(PropertyEditor), typeof(UITypeEditor))]
		[Category("Events")]
		public DualEvent<BindEventArgs> BindItem { get { return this.GetDualEvent<BindEventArgs>("BindItem"); } }
		/// <summary>
		/// Raised when a custom command is ordered on the client.
		/// </summary>
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		[Editor(typeof(PropertyEditor), typeof(UITypeEditor))]
		[Category("Events")]
		public ServerEvent<RepeaterCommandEventArgs> Command { get { return this.GetServerEvent<RepeaterCommandEventArgs>("Command"); } }
		/// <summary>
		/// This event is raised when the control needs to parse the HTML of the rendered items and add them to its internal array.
		/// </summary>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		[Editor(typeof(PropertyEditor), typeof(UITypeEditor))]
		[Category("Events")]
		public ClientEvent ParseRenderedItems { get { return this.GetEvent<ClientEvent>("ParseRenderedItems"); } }
		/// <summary>
		/// Raised when an item is added on the client.
		/// </summary>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[Editor(typeof(PropertyEditor), typeof(UITypeEditor))]
		[Category("Events")]
		public ServerEvent<RepeaterItemEventArgs> ItemAdded { get { return this.GetServerEvent<RepeaterItemEventArgs>("ItemAdded"); } }
		/// <summary>
		/// Raised when an item is deleted on the client.
		/// </summary>
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		[Editor(typeof(PropertyEditor), typeof(UITypeEditor))]
		[Category("Events")]
		public ServerEvent<RepeaterItemEventArgs> ItemDeleted { get { return this.GetServerEvent<RepeaterItemEventArgs>("ItemDeleted"); } }
		/// <summary>
		/// Raised when an item is updated on the client.
		/// </summary>
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		[Editor(typeof(PropertyEditor), typeof(UITypeEditor))]
		[Category("Events")]
		public ServerEvent<RepeaterItemEventArgs> ItemUpdated { get { return this.GetServerEvent<RepeaterItemEventArgs>("ItemUpdated"); } }
		/// <summary>
		/// Raised before the <see cref="Command"/> event is raised in order to get the command argument from a data entry.
		/// </summary>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		[Editor(typeof(PropertyEditor), typeof(UITypeEditor))]
		[Category("Events")]
		public ClientEvent GetArgumentByEntry { get { return this.GetEvent<ClientEvent>("GetArgumentByEntry"); } }
		/// <summary>
		/// Raised when client-side needs more entries (typically during switching pages).
		/// </summary>
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		[Editor(typeof(PropertyEditor), typeof(UITypeEditor))]
		[Category("Events")]
		public ServerEvent<RepeaterEntriesEventArgs> LoadEntries { get { return this.GetServerEvent<RepeaterEntriesEventArgs>("LoadEntries"); } }
		/// <summary>
		/// Raised when the value is output to the client during rendering or AJAX requests; handler needs to JSON-serialize the data.
		/// </summary>
		public event EventHandler<SerializeEventArgs> Serialize;
		/// <summary>
		/// Raised when the value is input from the client during AJAX requests; handler needs to instantiate a data entry and JSON-deserialize the data.
		/// </summary>
		public event EventHandler<DeserializeEventArgs> Deserialize;
		/// <summary>
		/// Raised when the value is input from the client during AJAX requests; handler needs to instantiate a data entry.
		/// </summary>
		public event EventHandler<RepeaterItemEventArgs> CreateEntry;
		#endregion
		#region PROPS
		#region DEF
		/// <summary>
		/// Props.
		/// </summary>
		protected static new Properties _clientProperties = Define(BaseComplexValueControl<RepeaterValue>._clientProperties,
			new BasePropertyDefinition[] {
				new JsonPropertyDefinition<PagerSettings>("Pager", "P", null),
				new PropertyDefinition<int>("PageIndex", "PI", 0),
				new PropertyDefinition<bool>("DisplayNewItem", "DNI", false),
				new PropertyDefinition<bool>("DisplayEdit", "DE", false),
				new EnumPropertyDefinition<SortDirection>("SortDirection", "SD", SortDirection.Ascending),
				new EnumPropertyDefinition<SortDirection>("DefaultSortDirection", "DD", SortDirection.Ascending),
				new PropertyDefinition<string>("SortExpression", "SE", null),
				new PropertyDefinition<SortingMode>("SortingMode", "SM", SortingMode.Automatic),
			});
		/// <summary>
		/// Props.
		/// </summary>
		public override Properties ClientProperties
		{
			get
			{
				return _clientProperties;
			}
		}
		#endregion
		#region TEMPLATES
		/// <summary>
		/// The template to be used for the header.
		/// </summary>
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[Browsable(false)]
		public SingleInstanceTemplate Header { get { return _header; } }
		/// <summary>
		/// The template to be used for the footer.
		/// </summary>
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[Browsable(false)]
		public SingleInstanceTemplate Footer { get { return _footer; } }
		/// <summary>
		/// The template to be used when viewing an item.
		/// </summary>
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[Browsable(false)]
		public MultipleInstanceTemplate ViewItem { get { return _viewItem; } }
		/// <summary>
		/// The template to be used when editing an item.
		/// </summary>
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[Browsable(false)]
		public MultipleInstanceTemplate EditItem { get { return _editItem; } }
		#endregion
		#region VALUE
		/// <summary>
		/// Gets or sets an instance of <see cref="RepeaterValue"/> class which is used as the <see cref="Repeater"/>'s value.
		/// </summary>
		[Browsable(false)]
		public override RepeaterValue Value
		{
			get
			{
				return _value;
			}
			set
			{
				_value = value;
				_value._owner = this;
			}
		}
		/// <summary>
		/// Gets the collection of <see cref="PropertyPair"/>s which specify how each property of a data entry is serialized.
		/// </summary>
		[PersistenceMode(PersistenceMode.InnerProperty)]
		public List<PropertyPair> Fields { get { return this._fields; } }
		#endregion
		#region DISPLAY
		/// <summary>
		/// Controls how paging is performed.
		/// </summary>
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		[PersistenceMode(PersistenceMode.InnerProperty)]
		[Editor(typeof(PropertyEditor), typeof(UITypeEditor))]
		public PagerSettings Pager
		{
			get
			{
				return GetProperty<PagerSettings>("Pager");
			}
		}
		/// <summary>
		/// The index of the currently displayed page in a multi-page row set.
		/// </summary>
		[DefaultValue(0)]
		public int PageIndex
		{
			get
			{
				return GetProperty<int>("PageIndex");
			}
			set
			{
				ViewState["PageIndex"] = value;
			}
		}
		/// <summary>
		/// Gets the actual page index within the boundaries of available pages.
		/// </summary>
		[Browsable(false)]
		public int ActualPageIndex
		{
			get
			{
				return Math.Max(0, Math.Min(this.NumPages - 1, this.PageIndex));
			}
		}
		/// <summary>
		/// Gets the number of pages required to display all of the entries, or 1 if paging is off (Pager.Size == 0).
		/// </summary>
		[Browsable(false)]
		public int NumPages
		{
			get
			{
				if (this.Pager.Size == 0)
					return 1;
				return (this.Value.NumEntries - 1) / this.Pager.Size + 1;
			}
		}
		/// <summary>
		/// Returns the range of page indexes of displayed page buttons, basing on ActualPageIndex and Pager.ButtonCount.
		/// </summary>
		[Browsable(false)]
		public IntRange PagesRange
		{
			get
			{
				return new IntRange(
					Math.Max(0, Math.Min(this.ActualPageIndex - this.Pager.ButtonCount / 2, this.NumPages - this.Pager.ButtonCount)),
					Math.Min(this.NumPages, Math.Max(this.ActualPageIndex + this.Pager.ButtonCount / 2 + 1, this.Pager.ButtonCount)));
			}
		}
		/// <summary>
		/// The direction in which the row set is sorted.
		/// </summary>
		[DefaultValue(SortDirection.Ascending)]
		public SortDirection SortDirection
		{
			get
			{
				return GetProperty<SortDirection>("SortDirection");
			}
			set
			{
				ViewState["SortDirection"] = value;
			}
		}
		/// <summary>
		/// The direction in which the row set is sorted when SortExpression is changed.
		/// </summary>
		[DefaultValue(SortDirection.Ascending)]
		public SortDirection DefaultSortDirection
		{
			get
			{
				return GetProperty<SortDirection>("DefaultSortDirection");
			}
			set
			{
				ViewState["DefaultSortDirection"] = value;
			}
		}
		/// <summary>
		/// The expresseion used to sort the row set.
		/// </summary>
		public string SortExpression
		{
			get
			{
				return GetProperty<string>("SortExpression");
			}
			set
			{
				ViewState["SortExpression"] = value;
			}
		}
		/// <summary>
		/// If Automatic, the control will first try sorting on the client if it has the whole row set loaded. If Server, it will contact the server in any case.
		/// </summary>
		[DefaultValue(SortingMode.Automatic)]
		public SortingMode SortingMode
		{
			get
			{
				return GetProperty<SortingMode>("SortingMode");
			}
			set
			{
				ViewState["SortingMode"] = value;
			}
		}
		/// <summary>
		/// Display a new empty edit item after value set.
		/// </summary>
		[DefaultValue(false)]
		public bool DisplayNewItem
		{
			get
			{
				return GetProperty<bool>("DisplayNewItem");
			}
			set
			{
				ViewState["DisplayNewItem"] = value;
			}
		}
		/// <summary>
		/// Display all items in edit mode by default (for ex., after value set).
		/// </summary>
		[DefaultValue(false)]
		public bool DisplayEdit
		{
			get
			{
				return GetProperty<bool>("DisplayEdit");
			}
			set
			{
				ViewState["DisplayEdit"] = value;
			}
		}
		/// <summary>
		/// Gets the range of entries that are displayed as part of the current page.
		/// </summary>
		[Browsable(false)]
		public IntRange EntriesRange
		{
			get
			{
				if (this.Pager.Size == 0)
				{
					return new IntRange(0, this.Value.NumEntries);
				}
				else
				{
					return new IntRange(this.Pager.Size * this.ActualPageIndex, Math.Min(this.Value.NumEntries, this.Pager.Size * (this.ActualPageIndex + 1)));
				}
			}
		}
		/// <summary>
		/// Gets or sets a flag specifying if the items are rendered by the server upon displaying the page.
		/// </summary>
		public bool EnableServerRender { get; set; }
		#endregion
		#endregion
		#region METHODS
		/// <summary>
		/// Checks if the entries at the specified range are loaded and if not, raises the <see cref="LoadEntries"/> event to load them.
		/// </summary>
		/// <param name="range"></param>
		/// <returns></returns>
		public IntRange EnsureEntriesLoaded(IntRange range)
		{
			if (this.Value.Entries.Count + this.Value.FirstIndex < range.End)
			{
				if (this.LoadEntries.Active)
				{
					int missingStartRow = Math.Max(this.Value.Entries.Count, range.Start),
						missingNumRows = range.End - missingStartRow;
					RepeaterEntriesEventArgs e = new RepeaterEntriesEventArgs(this, missingStartRow, missingNumRows);
					this.LoadEntries.Raise(this, e);

					while (this.Value.Entries.Count <= range.Start)
						this.Value.Entries.Add(null);

					this.Value.Entries.AddRange(e.Entries);
				}
				return new IntRange(range.Start, this.Value.Entries.Count);
			}
			return range;
		}
		/// <summary>
		/// Binds the specified data entry to the template instantiated in the specified container control.
		/// </summary>
		/// <param name="container"></param>
		/// <param name="dataEntry"></param>
		public virtual void BindDataToItem(MarkupTemplate template, IRepeaterEntries entries, int iEntry)
		{
			template.Bind(propertyPath => entries.GetValue(iEntry, propertyPath));

			this.OnBindItem(new BindEventArgs(template.Container, entries[iEntry]));
		}
		#endregion
	}
}